package com.open.center.jwt.provider;

import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import com.open.center.jwt.config.JwtConfig;
import com.open.center.jwt.config.JwtField;
import com.open.center.jwt.exception.JwtExpiredException;
import com.open.center.jwt.model.JwtToken;
import com.open.center.jwt.model.JwtUser;


/**
 * Jwt Token Provider
 *
 * @author Riche's
 * @since 2025/5/23
 */
@RequiredArgsConstructor
@Service
public class JwtProvider {

    private final JwtConfig jwtConfig;

    public Boolean verify(String token) throws ParseException, JOSEException {
        // 公钥验签
        JWSVerifier verifier = new RSASSAVerifier(jwtConfig.getPublicKey());
        SignedJWT parseJWT = SignedJWT.parse(token);
        return parseJWT.verify(verifier);
    }

    public JwtClaim parseClaim(String token) throws ParseException, JOSEException, JwtExpiredException {
        // 解析Token内容
        JWSVerifier verifier = new RSASSAVerifier(jwtConfig.getPublicKey());
        SignedJWT parseJWT = SignedJWT.parse(token);
        if (parseJWT.verify(verifier)) {
            JwtClaim claim = new JwtClaim();
            claim.setIssuer(parseJWT.getJWTClaimsSet().getIssuer());
            claim.setSubject(parseJWT.getJWTClaimsSet().getSubject());
            claim.setExpirationTime(parseJWT.getJWTClaimsSet().getExpirationTime());
            claim.setNotBefore(parseJWT.getJWTClaimsSet().getNotBeforeTime());
            claim.setIssuedAt(parseJWT.getJWTClaimsSet().getIssueTime());
            claim.setJwtId(parseJWT.getJWTClaimsSet().getJWTID());
            claim.getter().putAll(parseJWT.getJWTClaimsSet().getClaims());
            claim.setter("isAuthenticated", true);
            return claim;
        }
        throw new JwtExpiredException("Expired JWT token.");
    }

    public JwtUser parseUser(String token) throws ParseException, JOSEException, JwtExpiredException {
        // 解析Token内容
        JWSVerifier verifier = new RSASSAVerifier(jwtConfig.getPublicKey());
        SignedJWT parseJWT = SignedJWT.parse(token);
        if (parseJWT.verify(verifier)) {
            JwtUser user = new JwtUser();
            if (parseJWT.getJWTClaimsSet().getClaim(JwtField.USERID.getId()) != null) {
                user.setUserId(parseJWT.getJWTClaimsSet().getClaim(JwtField.USERID.getId()).toString());
            }
            if (parseJWT.getJWTClaimsSet().getClaim(JwtField.USERNAME.getId()) != null) {
                user.setUsername(parseJWT.getJWTClaimsSet().getClaim(JwtField.USERNAME.getId()).toString());
            }
            if (parseJWT.getJWTClaimsSet().getClaim(JwtField.REALNAME.getId()) != null) {
                user.setRealName(parseJWT.getJWTClaimsSet().getClaim(JwtField.REALNAME.getId()).toString());
            }
            user.getter().putAll(parseJWT.getJWTClaimsSet().getClaims());
            user.setAuthenticated(true);
            return user;
        }
        throw new JwtExpiredException("Expired JWT token.");
    }


    public JwtToken createToken(String userId, String username, String realName) throws JOSEException {
        // 私钥签名
        JWSSigner signer = new RSASSASigner(jwtConfig.getPrivateKey());
        JWTClaimsSet claimsSet = builder()
                .claim(JwtField.USERID.getId(), userId)
                .claim(JwtField.USERNAME.getId(), username)
                .claim(JwtField.REALNAME.getId(), realName)
                .build();
        SignedJWT signedJWT = new SignedJWT(new JWSHeader
                .Builder(JWSAlgorithm.RS256).build(), claimsSet);
        signedJWT.sign(signer);
        return new JwtToken(claimsSet.getJWTID(), claimsSet.getExpirationTime().getTime(), signedJWT.serialize());
    }

    public JwtToken createToken(Map<String, Object> payload) throws JOSEException {
        // 私钥签名
        JWSSigner signer = new RSASSASigner(jwtConfig.getPrivateKey());
        JWTClaimsSet.Builder builder = builder();
        putClaims(builder, payload);
        JWTClaimsSet claimsSet = builder.build();
        SignedJWT signedJWT = new SignedJWT(new JWSHeader
                .Builder(JWSAlgorithm.RS256).build(), claimsSet);
        signedJWT.sign(signer);
        return new JwtToken(claimsSet.getJWTID(), claimsSet.getExpirationTime().getTime(), signedJWT.serialize());
    }

    public JwtToken createToken(Map<String, Object> payload, Date exp) throws JOSEException {
        // 私钥签名
        JWSSigner signer = new RSASSASigner(jwtConfig.getPrivateKey());
        JWTClaimsSet.Builder builder = builder();
        builder.expirationTime(exp);
        putClaims(builder, payload);
        JWTClaimsSet claimsSet = builder.build();
        SignedJWT signedJWT = new SignedJWT(new JWSHeader
                .Builder(JWSAlgorithm.RS256).build(), claimsSet);
        signedJWT.sign(signer);
        return new JwtToken(claimsSet.getJWTID(), claimsSet.getExpirationTime().getTime(), signedJWT.serialize());
    }

    private void putClaims(JWTClaimsSet.Builder builder, Map<String, Object> payload) {
        if (payload != null) {
            payload.forEach(builder::claim);
        }
    }

    private JWTClaimsSet.Builder builder() {
        return new JWTClaimsSet.Builder()
                .issuer("https://www.open.com")
                .subject("Open Association")
                .audience("All")
                .expirationTime(exp())
                .notBeforeTime(now())
                .issueTime(now())
                .jwtID(UUID.randomUUID().toString());
    }


    private static final String ZONE = "Asia/Shanghai";

    private Date now() {
        return Date.from(LocalDateTime.now().atZone(ZoneId.of(ZONE)).toInstant());
    }

    public Date exp() {
        return Date.from(LocalDateTime.now()
                .plusHours(jwtConfig.getJwtProperties().getExpTime())
                .atZone(ZoneId.of(ZONE)).toInstant());
    }

    public Date exp(Long hours) {
        return Date.from(LocalDateTime.now()
                .plusHours(hours)
                .atZone(ZoneId.of(ZONE)).toInstant());
    }

    public long getExpTime() {
        return jwtConfig.getJwtProperties().getExpTime();
    }
}
